﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using net.veekee.common.reflection;
using net.veekee.common.designpattern;

namespace net.veekee.common
{
    public delegate bool MandatoryOptionValidatorHandler(object sender, params Object[] args);

    public class OptionValidatorHandler
    {
        public static bool IsMandatory(object sender, params Object[] args)
        {
            return true;
        }

        public static bool IsNotMandatory(object sender, params Object[] args)
        {
            return false;
        }
    }

    public class Option
    {
        public String Name = "";
        public bool NoArgument = false;
        public bool Found = false;
        public bool FileMustExist = false;
        public String Description = "";
        public MandatoryOptionValidatorHandler MandatoryValidation = null;
        private String innerValue = null;
        public String Value
        {
            get
            {
                if (this.innerValue == null)
                    return this.DefaultValue;
                return this.innerValue;
            }
            set
            {
                this.innerValue = value;
            }
        }
        public String DefaultValue = null;
        public String[] PossibleValues = new String[0];

        public override string ToString()
        {
            return this.Name + "=>" + this.Value;
        }
    }

    public class Options : Singleton<Options>
    {
        public static String GetVersion(Type t)
        {
            Assembly entry = t.Assembly;
            if (entry == null)
            {
                return "unknown";
            }
            return String.Format("Build: {0} / Compilation: {1}", entry.GetName().Version.ToString(2), DLLInterop.GetAssemblyCompilationTime(entry.Location));
        }

        private List<Option> options = null;
        private List<String> loadedConfigFiles = null;

        private bool noArgumentsPassed = true;
        public bool NoArgumentsPassed
        {
            get
            {
                return this.noArgumentsPassed;
            }
        }

        public Options()
        {
            this.options = new List<Option>();
            this.loadedConfigFiles = new List<String>();
        }

        public void Add(Option o)
        {
            this.options.Add(o);
        }

        public void LoadConfigFile(String filename)
        {
            if (!System.IO.File.Exists(filename))
            {
                Log.GetInstance().Error("Config file '{0}' does not exist.", filename);
                return;
            }
            String[] lines = System.IO.File.ReadAllLines(filename);
            this.Load("Config file", lines);
        }

        public void LoadConfigFiles(String filename)
        {
            if (filename == null)
                return;
            String[] configfiles = filename.Split(';');
            foreach (String config in configfiles)
            {
                if (!this.loadedConfigFiles.Contains(config))
                {
                    this.loadedConfigFiles.Add(config);
                    this.LoadConfigFile(config);
                }
                else
                {
                    Log.GetInstance().Warn("Config load: Infinite loop prevented as config file '{0}' has already been parsed.", config);
                }
            }
        }

        public void Load(String source, String[] args)
        {
            String[] a = null;
            String argtmp = null;

            Log.GetInstance().Debug("Loading option source '{0}'.", source);
            foreach (String arg in args)
            {
                argtmp = arg.Trim();
                if (argtmp.Length == 0)
                    continue;
                if (argtmp.StartsWith("--"))
                    argtmp = argtmp.Substring(2);
                a = argtmp.Split(new char[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries);
                a[0] = a[0].ToLower();
                Option optFound = this.options.Find(delegate(Option o)
                {
                    return (o.Name.ToLower().CompareTo(a[0]) == 0);
                });
                if (optFound == null)
                {
                    if (this.Get("config.custom", "false").Equals("true"))
                    {
                        optFound = new Option() { Name = a[0], NoArgument = (a.Length == 1), MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), Description = "Autogenerated option" };
                        this.Add(optFound);
                    }
                    else
                    {
                        Log.GetInstance().Debug("Skipping argument '{0}'.", a[0]);
                        continue;
                    }
                }
                if (a.Length == 2)
                    optFound.Value = a[1];
                else
                {
                    if(! optFound.NoArgument)
                        optFound.Value = "";
                }
                optFound.Found = true;
                this.noArgumentsPassed = false;

                Log.GetInstance().Debug("Found argument '{0}' (value={1}).", optFound.Name, optFound.Value);

                if (optFound.Name.Equals("config"))
                {
                    this.LoadConfigFiles(optFound.Value);
                }
            }
        }

        public String Get(String name, String defaultValue = null)
        {
            name = name.ToLower();
            Option optFound = this.options.Find(delegate(Option o)
            {
                return (o.Name.ToLower().CompareTo(name) == 0);
            });
            if (optFound == null)
                return defaultValue;
            if (!optFound.Found)
            {
                if (optFound.Value != null)
                    return optFound.Value;
                return defaultValue;
            }
            return optFound.Value;
        }

        public void Set(String name, String Value = null)
        {
            name = name.ToLower();
            Option optFound = this.options.Find(delegate(Option o)
            {
                return (o.Name.ToLower().CompareTo(name) == 0);
            });
            if (optFound == null)
                return;
            optFound.Found = true;
            if (Value != null)
                optFound.Value = Value;
        }

        public bool IsEnabled(String name)
        {
            name = name.ToLower();
            Option optFound = this.options.Find(delegate(Option o)
            {
                return (o.Name.ToLower().CompareTo(name) == 0);
            });
            if (optFound == null)
                return false;
            return optFound.Found;

        }

        public bool Check()
        {
            bool res = true;
            foreach (Option o in this.options)
            {
                if (o.MandatoryValidation(o.Name) && !o.Found)
                {
                    res = false;
                    Log.GetInstance().Error("{0} is mandatory", o.Name);
                    continue;
                }
                if (o.FileMustExist)
                {
                    if (!System.IO.File.Exists(o.Value))
                    {
                        res = false;
                        Log.GetInstance().Error("{0} must be a valid file", o.Name);
                        continue;
                    }
                }
                if (o.PossibleValues.Length > 0)
                {
                    if (o.Value == null)
                    {
                        if (o.DefaultValue != null)
                            o.Value = o.DefaultValue;
                        continue;
                    }
                    String gotit = Array.Find<String>(o.PossibleValues, delegate(String s)
                    {
                        return (s.ToLower().CompareTo(o.Value.ToLower()) == 0);
                    });
                    if (gotit == null)
                    {
                        res = false;
                        Log.GetInstance().Error("Value of {0} argument is not set within possible range", o.Name);
                    }
                }
            }
            return res;
        }

        public void Usage()
        {
            String appender = "";
            StringBuilder sb = new StringBuilder();

            Log.GetInstance().Info("Usage:");
            sb.Append("<Application name.exe>");
            foreach (Option o in this.options)
            {
                sb.Append(String.Format(" {0}--{1}{2}{3}", (o.MandatoryValidation(o.Name) ? "" : "["), o.Name, (o.NoArgument ? "" : "=<value>"), (o.MandatoryValidation(o.Name) ? "" : "]")));
            }
            Log.GetInstance().Info(sb.ToString());
            foreach (Option o in this.options)
            {
                Log.GetInstance().Info("\t{0}: {1}{2}", o.Name, o.Description, (o.MandatoryValidation(o.Name) ? "\n\t\tMandatory argument" : ""));
                if (o.PossibleValues.Length > 0)
                {
                    appender = "";
                    sb = new StringBuilder();
                    sb.Append("\t\tPossible values: ");
                    foreach (String v in o.PossibleValues)
                    {
                        sb.Append(appender + v);
                        appender = ", ";
                    }
                    if (o.DefaultValue != null)
                        sb.Append(" (default: " + o.DefaultValue + ")");
                    Log.GetInstance().Info(sb.ToString());
                }
            }
        }
    }
}
